package com.example.blockchain.controller;

import com.example.blockchain.entity.JSONObject;
import com.example.blockchain.entity.TradeBO;
import com.example.blockchain.entity.TradeObject;
import com.example.blockchain.entity.block.*;
import com.example.blockchain.entity.block.Dictionary;
import com.example.blockchain.service.impl.BlockServiceImpl;
import com.example.blockchain.service.impl.DicServiceImpl;
import com.example.blockchain.service.impl.PendingServiceImpl;
import com.example.blockchain.thread.WorkThread;
import com.example.blockchain.timer.UpdateTimer;
import com.example.blockchain.utils.DataUtils;
import com.example.blockchain.utils.DateUtils;
import com.example.blockchain.utils.InitUtils;
import com.example.blockchain.utils.encrypt.EncryptUtil;
import com.example.blockchain.utils.encrypt.EthUtils;
import com.google.gson.Gson;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import org.web3j.crypto.Sign;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@RestController
public class BlockController {

    @Resource
    private Mining mining;

    @Resource
    private MapFriends map;

    @Resource
    private RestTemplate restTemplate;

    @Resource
    private WorkThread workThread;


    @RequestMapping(value = "/starMining")
    public void starMining() {
        if (mining.updateComplete) {
            mining.isWork = true;
            workThread.start();
        } else {
            System.out.println("等待区块更新");
        }
    }

    @RequestMapping(value = "/data/getWallet")
    public ResponseEntity<JSONObject> getWallet() throws Exception {
        JSONObject jo = new JSONObject();

        EthWallet wallet = EthUtils.getWallet();

        jo.setO(wallet);
        jo.setMsg("生成钱包成功");
        jo.setCode("1");
        return new ResponseEntity<JSONObject>(jo, HttpStatus.OK);
    }


    @RequestMapping(value = "/data/getTradeObject", method = {RequestMethod.POST})
    public ResponseEntity<JSONObject> getTradeObject(@RequestBody TradeBO tradeBO) throws Exception {
        JSONObject jo = new JSONObject();

        if ("".equals(tradeBO.getContent()) || tradeBO.getContent() == null){
            jo.setCode("-1");
            jo.setMsg("交易content值不能为空");
            return new ResponseEntity<JSONObject>(jo, HttpStatus.OK);
        }
        if ("".equals(tradeBO.getFrom()) || tradeBO.getFrom() == null){
            jo.setCode("-1");
            jo.setMsg("交易from值不能为空");
            return new ResponseEntity<JSONObject>(jo, HttpStatus.OK);
        }
        if ("".equals(tradeBO.getPrivateKey()) || tradeBO.getPrivateKey() == null){
            jo.setCode("-1");
            jo.setMsg("私钥privateKey不能为空");
            return new ResponseEntity<JSONObject>(jo, HttpStatus.OK);
        }

        BigInteger pri = new BigInteger(tradeBO.getPrivateKey(), 16);

        TradeObject tradeObject = new TradeObject();
        tradeObject.setFrom(tradeBO.getFrom());
        tradeObject.setTo("system");
        tradeObject.setType("1");
        tradeObject.setContent(tradeBO.getContent());
        tradeObject.setJsoncreatetime(DateUtils.getTime());
        tradeObject.setObjToString(tradeObject.toString());


        Sign.SignatureData signatureData = EthUtils.signMessage(tradeObject.toString(),pri);
        String sign = EthUtils.getSignStr(signatureData);
        tradeObject.setSign(sign);

        jo.setO(tradeObject);
        jo.setMsg("签名成功");
        jo.setCode("1");
        return new ResponseEntity<JSONObject>(jo, HttpStatus.OK);
    }


    @RequestMapping(value = "/data/trade", method = {RequestMethod.POST})
    public ResponseEntity<JSONObject> trade(@RequestBody TradeObject tradeObject) {
        JSONObject jo = new JSONObject();
        List<Pending> pes = PendingServiceImpl.queryPendings();


        if ("".equals(tradeObject.getFrom()) || tradeObject.getFrom() == null){
            jo.setCode("-1");
            jo.setMsg("交易from值不能为空");
            return new ResponseEntity<JSONObject>(jo, HttpStatus.OK);
        }
        if("".equals(tradeObject.getTo()) || tradeObject.getTo() == null){
            jo.setCode("-1");
            jo.setMsg("交易to值不能为空");
            return new ResponseEntity<JSONObject>(jo, HttpStatus.OK);
        }
        if ("".equals(tradeObject.getObjToString()) || tradeObject.getObjToString() == null){
            jo.setCode("-1");
            jo.setMsg("交易objToString值不能为空");
            return new ResponseEntity<JSONObject>(jo, HttpStatus.OK);
        }
        if ("".equals(tradeObject.getSign()) || tradeObject.getSign() == null){
            jo.setCode("-1");
            jo.setMsg("交易sign值不能为空");
            return new ResponseEntity<JSONObject>(jo, HttpStatus.OK);
        }
        if ("".equals(tradeObject.getType()) || tradeObject.getType() == null){
            jo.setCode("-1");
            jo.setMsg("交易type值不能为空");
            return new ResponseEntity<JSONObject>(jo, HttpStatus.OK);
        }


        String no = PendingServiceImpl.genTradeNo(tradeObject);
        tradeObject.setHashNo(no);


        for (Pending p : pes) {
            if (p.getOrderNo().equals(no)) {
                jo.setCode("-1");
                jo.setMsg("交易已存在");
                return new ResponseEntity<JSONObject>(jo, HttpStatus.OK);
            }
        }

        Sign.SignatureData signatureData = EthUtils.stringToSignatureData(tradeObject.getSign());
        //验证钱包地址
        boolean isValid = EthUtils.verifySignature(tradeObject.getObjToString(), signatureData, tradeObject.getFrom());
        if (!isValid){
            jo.setCode("-1");
            jo.setMsg("验签失败，请重新签名");
            return new ResponseEntity<JSONObject>(jo, HttpStatus.OK);
        }


        //tradeObject.setObjToString("");
        String body = new Gson().toJson(tradeObject);
        try {
            PendingServiceImpl.validateTradeNo(tradeObject);
            Pending pending = new Pending();
            pending.setTradeBody(body);
            pending.setCreateTime(tradeObject.getJsoncreatetime());
            pending.setOrderNo(tradeObject.getHashNo());
            pending.setTradeType(tradeObject.getType());
            PendingServiceImpl.save(pending);
            for (String port : map.getFs().keySet()) {
                Friends f = map.getFs().get(port);
                String ip = f.getIp();
                String url = "http://" + ip + ":8001/data/trade";
                restTemplate.postForEntity(url, tradeObject, TradeObject.class);
            }
            jo.setCode("1");
            jo.setMsg("成功");

        } catch (Exception e) {
            System.out.println(e.getMessage());
            jo.setCode("-1");
            jo.setMsg("失败");
        }
        return new ResponseEntity<JSONObject>(jo, HttpStatus.OK);

    }

    @CrossOrigin
    @RequestMapping(value = "/block/getBlockData")
    public ResponseEntity<JSONObject> getBlockData(@RequestParam("blockIndex") Integer blockIndex){
        JSONObject jo = new JSONObject();

        if (blockIndex==-1){
            //返回最新的5条
            List<Block> list = BlockServiceImpl.queryBlockData("block0");
            if (list != null && list.size() > 0){
                Collections.reverse(list);
            }
            jo.setO(list);
            jo.setCode("1");
            jo.setMsg("查询成功");
        }else {
            //查询指定
            List<Block> blocks = BlockServiceImpl.queryBlockByBlockIndex(String.valueOf(blockIndex));
            if (blocks.size() > 0){
                Block block = blocks.get(0);
                jo.setO(block);
                jo.setCode("1");
                jo.setMsg("查询成功");
            }else {
                jo.setCode("-1");
                jo.setMsg("暂无数据");
            }
        }
        return new ResponseEntity<JSONObject>(jo, HttpStatus.OK);
    }

    @CrossOrigin
    @RequestMapping(value = "/block/getBlockDetail")
    public ResponseEntity<JSONObject> getBlockDetail(@RequestParam("blockIndex") Integer blockIndex){
        JSONObject jo = new JSONObject();
        List<Block> blocks = BlockServiceImpl.queryBlockByBlockIndex(String.valueOf(blockIndex));
        if (blocks.size() > 0){

            Block block = blocks.get(0);
            String blockString = DataUtils.getBlockString(DataUtils.getRelativePath(block.getPath()));

            BlockFile blockFile = new Gson().fromJson(blockString, BlockFile.class);
            Map<String, TradeObject> tbMap = blockFile.getTbMap();

            ArrayList<TradeObject> list = new ArrayList<>();
            for (Map.Entry<String, TradeObject> entry : tbMap.entrySet()) {
                TradeObject value = entry.getValue();
                list.add(value);
            }

            String s = new Gson().toJson(list);
            block.setDataJson(s);

            jo.setO(block);
            jo.setCode("1");
            jo.setMsg("查询成功");
        }else {
            jo.setCode("-1");
            jo.setMsg("暂无数据");
        }
        return new ResponseEntity<JSONObject>(jo, HttpStatus.OK);
    }



    @RequestMapping(value = "/mining/checkBlock", method = {RequestMethod.POST})
    public ResponseEntity<JSONObject> checkBlock(@RequestBody Block b) {
        JSONObject jo = new JSONObject();

        Dictionary diffWorkload = InitUtils.intiDifficulty();// 字典表的工作量配置
        String blockHash = EncryptUtil.encryptSHA256(b.workString());
        List<Block> bs = BlockServiceImpl.queryBlockByBlockIndex(b.getBlockIndex());

        if (bs.size() != 0 || Integer.valueOf(b.getBlockIndex()) - 1 < UpdateTimer.currentMaxBlockIndex.intValue() || !blockHash.equals(b.getBlockHash()) || !blockHash.startsWith(diffWorkload.getValue())) {
            jo.setO(false);
        } else {
            mining.isWork = false;
            jo.setO(true);
        }

        jo.setCode("1");
        return new ResponseEntity<JSONObject>(jo, HttpStatus.OK);

    }


    @PostMapping(value = "/mining/server/block.zip")
    @ResponseBody
    public void blockdownload(HttpServletResponse response, @RequestBody NoticeParams noticeParams) {
        OutputStream responseBody = null;
        ZipOutputStream zos = null;
        try {
            List<Block> bs = BlockServiceImpl.queryBlockByBlockIndex(noticeParams.getBn());
            Block b = null;
            //
            if (bs != null && bs.size() > 0) {
                b = bs.get(0);
                if (b != null) {
                    zos = new ZipOutputStream(response.getOutputStream());
                    //写入块对象
                    zos.putNextEntry(new ZipEntry("blockObject"));
                    String boStr = new Gson().toJson(b);
                    byte[] boStrBytes = boStr.getBytes();
                    zos.write(boStrBytes, 0, boStrBytes.length);
                    //写入块文件
                    zos.putNextEntry(new ZipEntry("blockFile"));
                    String blockString = DataUtils.getBlockString(DataUtils.getRelativePath(b.getPath()));
                    byte[] bsStrBytes = blockString.getBytes();
                    zos.write(bsStrBytes, 0, bsStrBytes.length);
                    //写入主链上最高的编号
                    zos.putNextEntry(new ZipEntry("maxBlockIndex"));
                    Dictionary dic = DicServiceImpl.queryDic(Dictionary.MODUAL_BLOCK, Dictionary.CURRENTBLOCKINDEX);
                    byte[] maxStrBytes = dic.getValue().getBytes();
                    zos.write(maxStrBytes, 0, maxStrBytes.length);

                    zos.closeEntry();
                    zos.close();
                }
            }
        } catch (Exception e) {
            e.getMessage();
        } finally {
            if (responseBody != null) {
                try {
                    responseBody.close();
                } catch (IOException e) {
                    e.getMessage();
                }
            }
            if (zos != null) {
                try {
                    zos.close();
                } catch (IOException e) {
                    e.getMessage();
                }
            }
        }
    }


}
